/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package org.apache.asyncweb.server.session;

import org.apache.asyncweb.common.MutableHttpResponse;
import org.apache.asyncweb.server.HttpServiceContext;
import org.apache.asyncweb.server.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A simple <code>SessionAccessor</code> implementation which acts as a facade
 * to an employed <code>SessionIdentifier</code>, <code>SessionKeyFactory</code>
 * and <code>SessionStore</code>.<br/>
 * 
 * A Default identifier and key factory is employed by this accessor, but the
 * implementations used can be switched (if required) using the appropriate
 * setter methods.<br/>
 * 
 * 
 */
public class DefaultSessionAccessor implements HttpSessionAccessor {

	private static final Logger LOG = LoggerFactory
			.getLogger(DefaultSessionAccessor.class);

	private HttpSessionIdentifier identifier = new CookieIdentifier();

	private HttpSessionKeyFactory keyFactory;

	private HttpSessionStore store;

	/**
	 * Constructs with the default identifier and key factory
	 */
	public DefaultSessionAccessor() {
		keyFactory = new SecureRandomKeyFactory();
	}

	public HttpSession getSession(HttpServiceContext context, boolean create) {
		String sessionKey = identifier.getSessionKey(context.getRequest());
		HttpSession session = null;
		if (sessionKey != null) {
			if (LOG.isDebugEnabled())
				LOG.debug("Request contains session key - attempting to lookup");
			session = store.locateSession(sessionKey);
			if (session == null && LOG.isDebugEnabled()) {
				LOG.debug("No session found with request's session key");
			}
		}
		if (session == null && create) {
			if (LOG.isDebugEnabled())
				LOG.debug("No existing session found for request - creating new session");
			session = createNewSession();
		}
		return session;
	}

	public void addSessionIdentifier(HttpServiceContext context,
			MutableHttpResponse response) {
		HttpSession session = context.getSession(false);
		if (session == null) {
			return;
		}
		identifier.addSessionKey(session.getId(), response);
	}

	/**
	 * Sets the <code>SessionIdentifier</code> used for encoding / decoding
	 * session keys to / from requests. By default, a
	 * <code>CookieIdentifier</code> is employed
	 * 
	 * @param identifier
	 *            The identifier to be employed
	 */
	public void setSessionIdentifier(HttpSessionIdentifier identifier) {
		this.identifier = identifier;
	}

	/**
	 * Sets the <code>SessionKeyFactory</code> employed by this accessor for
	 * creating new session keys. By default, a
	 * <code>SecureRandomKeyFactory</code> is employed
	 * 
	 * @param keyFactory
	 *            The key factory to be employed
	 */
	public void setSessionKeyFactory(HttpSessionKeyFactory keyFactory) {
		this.keyFactory = keyFactory;
	}

	/**
	 * Sets the session store employed by this accessor
	 * 
	 * @param store
	 *            The store
	 */
	public void setSessionStore(HttpSessionStore store) {
		if (this.store != null) {
			this.store.close();
		}
		this.store = store;
	}

	/**
	 * Disposes of this accessor. We simply close our store
	 */
	public void dispose() {
		if (store != null)
			store.close();
		keyFactory.dispose();
	}

	/**
	 * Initialises this accessor. If we have not been configured with a session
	 * store, we simply employ a <code>BasicSessionStore</code> with a default
	 * time out
	 */
	public void init() {
		if (store == null) {
			if (LOG.isDebugEnabled())
				LOG.info("No session store configured. Employing default session store");
			store = new BasicSessionStore();
		}
		keyFactory.init();
	}

	/**
	 * Establishes a new session with the specified response. We employ our
	 * <code>SessionKeyFactory</code> to generate a new session key, and request
	 * our <code>SessionStore</code> to create a session based on this id.<br/>
	 * Our key factory is designed to not provide duplicate keys, but if this
	 * occurs we cycle until an unused key is located.
	 */
	private HttpSession createNewSession() {
		HttpSession session;
		String sessionKey;
		do {
			sessionKey = keyFactory.createSessionKey();
			session = store.createSession(sessionKey);
			if (session == null && LOG.isWarnEnabled()) {
				LOG.warn("SessionKeyFactory is providing duplicate keys!!");
			}
		} while (session == null);
		if (LOG.isDebugEnabled())
			LOG.debug("New session created");
		return session;
	}
}
